/*
 * Copyright 2013 JROSE
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jrose.core.jdbc.persistence.executor;

import org.apache.commons.lang3.StringUtils;
import org.jrose.core.jdbc.DatabaseConnection;
import org.jrose.core.jdbc.entity.EntityDefinition;
import org.jrose.core.jdbc.entity.field.PersistenceField;
import org.jrose.core.jdbc.persistence.annotation.RequireQuery;
import org.jrose.core.jdbc.persistence.annotation.SqlTemplate;
import org.jrose.core.jdbc.persistence.query.Sql;
import org.jrose.core.util.ExtendedMap;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;

import java.io.Serializable;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

@SqlTemplate("SELECT ${SELECT} FROM ${TABLE} ${QUERY}")
@RequireQuery
public class QuerySqlExecutor<E extends Serializable> extends AbstractSqlExecutor<E> {
    private static final ColumnMapRowMapper rowMapper = new ColumnMapRowMapper();

    public QuerySqlExecutor(DatabaseConnection databaseConnection, EntityDefinition<E> entityDefinition) {
        super(databaseConnection, entityDefinition);
    }

    @Override
    protected Sql generateNamedParameterSQL(String templateSQL) {
        String candidateSQL = templateSQL;
        List<String> columnNames = new LinkedList<>();
        for (PersistenceField field : entityDefinition.getAllFields()) {
            columnNames.add(field.getColumnName());
        }
        candidateSQL = StringUtils.replace(candidateSQL, "${SELECT}", StringUtils.join(columnNames, ","));

        Sql sql = this.query.generateQuerySQL(entityDefinition);
        candidateSQL = StringUtils.replace(candidateSQL, "${QUERY}", sql.getSqls().get(0));
        sql.setCandidateSQL(candidateSQL);
        return sql;
    }

    @Override
    protected ExtendedMap doExecute(Sql sql) {
        NamedParameterJdbcTemplate namedParameterJdbcTemplate = databaseConnection.getNamedParameterJdbcTemplate();
        MapSqlParameterSource parameterSource = new MapSqlParameterSource();
        parameterSource.addValues(sql.getNamedParameters());
        List<Map<String, Object>> rows = namedParameterJdbcTemplate.query(sql.getCandidateSQL(), parameterSource, rowMapper);

        Map<String, PersistenceField> fields = new LinkedHashMap<>();
        for (PersistenceField field : entityDefinition.getAllFields()) {
            fields.put(field.getColumnName(), field);
        }
        List<E> entities = new LinkedList<>();
        for (Map<String, Object> row : rows) {
            Class<E> entityClass = entityDefinition.getEntityClass();
            E entity;
            try {
                entity = entityClass.newInstance();
            } catch (InstantiationException | IllegalAccessException ex) {
                throw new RuntimeException("Entity '" + entityClass.getName() + "' must has public no parameter constructor");
            }

            for (String columnName : fields.keySet()) {
                PersistenceField field = fields.get(columnName);
                field.set(entity, row.get(columnName));
            }
            entities.add(entity);
        }
        return ExtendedMap.set("ENTITIES", entities);
    }
}
